package com.mss.media.config.shiro;

import com.mss.media.entity.MediaAdmin;
import com.mss.media.service.IAdminPermissionsService;
import com.mss.media.service.IMediaAdminService;
import com.mss.media.util.JwtUtil;
import com.mss.media.util.RedisUtil;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.springframework.stereotype.Component;

import java.util.Set;

/**
 * @Author 湫
 * @Date 2021/11/25 14:07
 * Name ShiroRealm
 */
@AllArgsConstructor
@Slf4j
@Component("realm")
public class ShiroRealm extends AuthorizingRealm {

    private final IMediaAdminService mediaAdminService;
    private final IAdminPermissionsService adminPermissionsService;
    private final RedisUtil redisUtil;


    /**
     * 必须重写此方法，不然Shiro会报错
     */
    @Override
    public boolean supports(AuthenticationToken token) {
        return token instanceof ShiroToken;
    }

    /**
     * JWT Token续签：
     * 业务逻辑：登录成功后，用户在未过期时间内继续操作，续签token。
     *         登录成功后，空闲超过过期时间，返回token已失效，重新登录。
     * 实现逻辑：
     *    1.登录成功后将token存储到redis里面(这时候k、v值一样都为token)，并设置过期时间为token过期时间
     *    2.当用户请求时token值还未过期，则重新设置redis里token的过期时间。
     *    3.当用户请求时token值已过期，但redis中还在，则JWT重新生成token并覆盖v值(这时候k、v值不一样了)，然后设置redis过期时间。
     *    4.当用户请求时token值已过期，并且redis中也不存在，则用户空闲超时，返回token已失效，重新登录。
     */
    public boolean tokenRefresh(String token, MediaAdmin mediaAdmin) {
        String cacheToken = String.valueOf(redisUtil.get(token));
        // 过期后会得到"null"值，所以需判断字符串"null"
        if (cacheToken != null && cacheToken.length() != 0 && !"null".equals(cacheToken)) {
            // 校验token有效性
            if (!JwtUtil.isVerify(cacheToken)) {
                // 生成token
                String newToken = JwtUtil.createToken(mediaAdmin.getAdminAccount(),mediaAdmin.getAdminName(),mediaAdmin.getAdminRoleId());
                // 将token存入redis,并设置超时时间
                redisUtil.set(token, newToken, JwtUtil.getExpireTime());
            } else {
                // 重新设置超时时间
                redisUtil.expire(token, JwtUtil.getExpireTime());
            }
            log.info("打印存入redis的过期时间："+redisUtil.ttl(token));
            return true;
        }
        return false;
    }

    /**
     * 用来进行身份认证，也就是说验证用户输入的账号和密码是否正确，
     * 获取身份验证信息，错误抛出异常
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken auth) throws AuthenticationException {
        log.info("————————身份认证——————————");
        String token = (String) auth.getCredentials();
        if (null == token) {
            throw new AuthenticationException("token不存在!");
        }
        if (!JwtUtil.isVerify(token)) {
            throw new AuthenticationException("无效token!");
        }
        // 解密获得username，用于和数据库进行对比
        String account = JwtUtil.getUserAccount(token);
        MediaAdmin mediaAdmin = mediaAdminService.selectByAccount(account);
        if (ObjectUtils.isEmpty(mediaAdmin)) {
            throw new AuthenticationException("用户不存在!");
        }
        if (!tokenRefresh(token, mediaAdmin)) {
            throw new AuthenticationException("Token已过期!");
        }
        redisUtil.expire(token,JwtUtil.getExpireTime());
        return new SimpleAuthenticationInfo(mediaAdmin, token,"realm");
    }
    /**
     * 获取用户权限信息，包括角色以及权限。
     * 只有当触发检测用户权限时才会调用此方法，例如checkRole,checkPermissionJwtToken
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        log.info("————权限认证 [ roles、permissions]————");
        /* 暂不编写，此处编写后，controller中可以使用@RequiresPermissions来对用户权限进行拦截*/
        MediaAdmin user = null;
        if (principals != null) {
            user = (MediaAdmin) principals.getPrimaryPrincipal();
        }
        SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
        if (user != null) {
            // 用户拥有的角色，比如“admin/user”
            String role = mediaAdminService.selectAdminRole(user.getAdminAccount());
            simpleAuthorizationInfo.addRole(role);
            log.info("角色为："+role);
            // 用户拥有的权限集合，比如“role:add,user:add”
            Set<String> permissions = adminPermissionsService.showPermissions(user.getAdminRoleId());
            simpleAuthorizationInfo.addStringPermissions(permissions);
            log.info("权限有："+permissions.toString());
        }
        return simpleAuthorizationInfo;
    }
}
